import jwt 
import time
import logging
from be.model import error
from be.model import db_conn
from sqlalchemy.exc import IntegrityError, SQLAlchemyError
from pymongo.errors import PyMongoError
from be.model.encrypt import encrypt
from sqlalchemy.sql import text

def jwt_encode(user_id: str, terminal: str) -> str:
    encoded = jwt.encode(
        {"user_id": user_id, "terminal": terminal, "timestamp": time.time()},
        key=user_id,
        algorithm="HS256",
    )
    return encoded

def jwt_decode(encoded_token, user_id: str) -> str:
    decoded = jwt.decode(encoded_token, key=user_id, algorithms="HS256")
    return decoded


class User(db_conn.DBConn):
    token_lifetime: int = 3600  # 3600 second

    def __init__(self):
        db_conn.DBConn.__init__(self)

    def __check_token(self, user_id, db_token, token) -> bool:
        try:
            if db_token != token:
                return False
            jwt_text = jwt_decode(encoded_token=token, user_id=user_id)
            ts = jwt_text["timestamp"]
            if ts is not None:
                now = time.time()
                if self.token_lifetime > now - ts >= 0:
                    return True
        
        except jwt.exceptions.InvalidSignatureError as e:
            logging.error(str(e))
            return False
    
    # 注册
    def register(self, user_id: str, password: str):
        try:
            terminal = "terminal_{}".format(str(time.time()))
            token = jwt_encode(user_id, terminal)
            password = encrypt(password)

            with self.conn.begin() as transaction:
                self.conn.execute(
                    text("INSERT into users(user_id, password, balance, token, terminal) "
                        "VALUES (:uid, :pw, 0, :tok, :ter);"),
                    {"uid": user_id, "pw": password, "tok": token, "ter": terminal}
                )
        except IntegrityError:
            return error.error_exist_user_id(user_id)
        return 200, "ok"

    def check_token(self, user_id: str, token: str) -> tuple[int, str]:
        cursor = self.conn.execute(text("SELECT token from users where user_id= :uid"), {"uid":user_id})
        row = cursor.fetchone()
        if row is None:
            return error.error_authorization_fail()
        db_token = row[0]
        if not self.__check_token(user_id, db_token, token):
            return error.error_authorization_fail()
        return 200, "ok"

    def check_password(self, user_id: str, password: str) -> tuple[int, str]:
        cursor = self.conn.execute(text("SELECT password from users where user_id= :uid"), {"uid":user_id})
        row = cursor.fetchone()
        if row is None:
            return error.error_authorization_fail()
        if encrypt(password) != row[0]:
            return error.error_authorization_fail()

        return 200, "ok"
    
    # 登录
    def login(self, user_id: str, password: str, terminal: str) -> tuple[int, str, str]:
        token = ""
        try:
            with self.conn.begin() as transaction:  
                code, message = self.check_password(user_id, password)
                if code != 200:
                    return code, message, ""

                token = jwt_encode(user_id, terminal)
                cursor = self.conn.execute(text(
                    "UPDATE users SET token = :tok, terminal = :ter WHERE user_id = :uid"),
                    {'tok': token, 'ter': terminal, 'uid': user_id})
                
                if cursor.rowcount == 0:
                    return error.error_authorization_fail() + ("", )

        except SQLAlchemyError as e:
            return 528, "{}".format(str(e)), ""
        except BaseException as e:
            return 530, "{}".format(str(e)), ""
        return 200, "ok", token

    # 登出
    def logout(self, user_id: str, token: str) -> bool:
        try:
            with self.conn.begin() as transaction:  
                code, message = self.check_token(user_id, token)
                if code != 200:
                    return code, message

                terminal = "terminal_{}".format(str(time.time()))
                dummy_token = jwt_encode(user_id, terminal)
                
                cursor = self.conn.execute(text(
                    "UPDATE users SET token = :tok, terminal = :ter WHERE user_id= :uid"),
                    {'tok': dummy_token, 'ter': terminal, 'uid': user_id})
                
                if cursor.rowcount == 0:
                    return error.error_authorization_fail()

        except SQLAlchemyError as e:
            return 528, "{}".format(str(e))
        except BaseException as e:
            return 530, "{}".format(str(e))
        return 200, "ok"

    # 注销
    def unregister(self, user_id: str, password: str) -> tuple[int, str]:
        try:
            with self.conn.begin() as transaction:  
                code, message = self.check_password(user_id, password)

                if code != 200:
                    return code, message

                cursor = self.conn.execute(text("DELETE from users where user_id= :uid"), {'uid': user_id})
                if cursor.rowcount == 1:
                    pass  
                else:
                    return error.error_authorization_fail()
                
        except SQLAlchemyError as e:
            return 528, "{}".format(str(e))
        except BaseException as e:
            return 530, "{}".format(str(e))
        return 200, "ok"

    # 修改密码
    def change_password(self, user_id: str, old_password: str, new_password: str) -> bool:
        try:
            with self.conn.begin() as transaction:  
                code, message = self.check_password(user_id, old_password)
                if code != 200:
                    return code, message
                
                terminal = "terminal_{}".format(str(time.time()))
                token = jwt_encode(user_id, terminal)

                new_password = encrypt(new_password)
                
                cursor = self.conn.execute(text(
                    "UPDATE users set password = :pw, token = :tok, terminal = :ter where user_id = :uid"),
                    {'pw': new_password, 'tok': token, 'ter': terminal, 'uid': user_id})
                
                if cursor.rowcount == 0:
                    return error.error_authorization_fail()
                
        except SQLAlchemyError as e:
            return 528, "{}".format(str(e))
        except BaseException as e:
            return 530, "{}".format(str(e))
        return 200, "ok"
